package com.google.code.pokerstats_service.math;

import edu.ucla.stat.SOCR.distributions.NormalDistribution;
import java.util.Map;
import java.util.Set;

import com.google.code.pokerstats_service.Game;
import com.google.code.pokerstats_service.PlayerResult;
import com.google.code.pokerstats_service.PlayerStats;

/**
 * Calculates players' RoRU (Risk of Ruin under Uncertainty).
 * @see "The Mathematics of Poker", p. 298
 * 
 * @author DL
 */
public class RiskOfRuinCalculator {
    /**
     * Constructor.
     * @param bankroll   Initial bankroll (e.g. 1000 money units).
     */
    public RiskOfRuinCalculator(double bankroll) {
        this.bankroll = bankroll;
    }
    
    public void updateStats(
	    Map<Game, Set<PlayerResult>> games, Map<Long, PlayerStats> playersStats) {
	
        for (PlayerStats stats : playersStats.values()) {
            if (stats.stdev > 0) {
                stats.riskOfRuin =
                    riskOfRuinU(stats.expectation, stats.stdev, stats.gamesPlayed);
            } else {
                stats.riskOfRuin = (stats.expectation > 0 ? 0.0 : 1.0);
            }
        }
    }
    
    /**
     * Calculates the RoR (Risk of Ruin) given a normally distributed profit
     * with estimated parameters (which are normally distributed themselves).
     * 
     * @param exp     Player profit expectation estimate.
     * @param stdev   Player profit standard deviation estimate.
     * @param n       Number of samples used for the estimates.
     * @return the calculated RoRU.
     */
    private double riskOfRuinU(double exp, double stdev, int n) {
        double stderr = stdev / Math.sqrt(n);
        
        double x1 = riskOfRuin(exp, stdev);
        double x2 = Math.exp(
            2*bankroll*bankroll*stderr*stderr / (stdev*stdev*stdev*stdev));
        double x3 = normDist.getCDF(
            (exp - 2*bankroll*stderr*stderr/(stdev*stdev)) / stderr);
        double x4 = normDist.getCDF(-exp / stderr);
        
        if (Double.isInfinite(x2)) {
            x2 = Double.MAX_VALUE;
        }
        
        return x1 * x2 * x3 + x4;
    }
    
    /**
     * Calculates the RoR (Risk of Ruin) given a normally distributed profit
     * with known parameters.
     * 
     * @param exp     Player profit expectation.
     * @param stdev   Player profit standard deviation.
     * @return the calculated RoR.
     */
    private double riskOfRuin(double exp, double stdev) {
        if (exp <= 0) {
            return 1.0;
        } else {
            return Math.exp(-2*exp*bankroll/(stdev*stdev));
        }
    }
    
    private final double bankroll;
    private final NormalDistribution normDist = new NormalDistribution();
}